Skip to content

Conversation

codeflash-ai[bot]
Copy link

@codeflash-ai codeflash-ai bot commented Oct 22, 2025

📄 29% (0.29x) speedup for messages_to_prompt_string in guardrails/utils/docs_utils.py

⏱️ Runtime : 498 microseconds 385 microseconds (best of 44 runs)

📝 Explanation and details

The optimized code achieves a 29% speedup through two key performance improvements:

1. String concatenation optimization: The original code uses messages_copy += content which creates a new string object on each iteration - an O(n²) operation for large inputs. The optimized version collects strings in a list and uses "".join(content_list) at the end, which is O(n) and much more efficient.

2. Reduced isinstance() calls: Instead of calling isinstance() twice per message (once for Prompt, once for Instructions), the optimized version uses a single call with a pre-defined tuple _prompt_types = (Prompt, Instructions). This reduces function call overhead.

3. Minimized attribute access: The optimized code assigns msg["content"] to content_obj once per iteration, avoiding repeated dictionary lookups.

The performance gains are most pronounced with large-scale inputs: test cases with 500-1000 messages show 33-40% speedups, while small inputs (1-3 messages) are slightly slower due to the setup overhead of creating the list and tuple. The optimization particularly shines when processing many messages, making it ideal for batch processing scenarios or applications handling extensive message histories.

All behavior is preserved - same outputs, same exception handling, and same type support for Prompt, Instructions, and plain strings.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 36 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
import typing as t

# imports
import pytest
from guardrails.utils.docs_utils import messages_to_prompt_string


# Minimal Prompt and Instructions mocks for testing
class Prompt:
    def __init__(self, source):
        self.source = source

class Instructions:
    def __init__(self, source):
        self.source = source
from guardrails.utils.docs_utils import messages_to_prompt_string

# unit tests

# ---------------- Basic Test Cases ----------------

def test_single_message_string_content():
    # Single message with plain string content
    messages = [{"role": "user", "content": "Hello, world!"}]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 1.03μs -> 1.27μs (19.3% slower)

def test_multiple_messages_string_content():
    # Multiple messages with plain string content
    messages = [
        {"role": "user", "content": "Hello, "},
        {"role": "assistant", "content": "world!"},
    ]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 1.27μs -> 1.44μs (12.1% slower)



def test_empty_message_list():
    # Empty list should return empty string
    messages = []
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 509ns -> 839ns (39.3% slower)

def test_empty_string_content():
    # Messages with empty string content
    messages = [
        {"role": "user", "content": ""},
        {"role": "assistant", "content": ""},
    ]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 1.22μs -> 1.46μs (16.5% slower)

def test_content_with_whitespace_only():
    # Messages with whitespace-only content
    messages = [
        {"role": "user", "content": "   "},
        {"role": "assistant", "content": "\n\t"},
    ]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 1.26μs -> 1.45μs (13.1% slower)

def test_content_with_special_characters():
    # Messages with special/unicode characters
    messages = [
        {"role": "user", "content": "¡Hola! "},
        {"role": "assistant", "content": "你好,世界!"},
    ]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 1.48μs -> 1.71μs (13.3% slower)

def test_content_with_newlines():
    # Messages with newlines in content
    messages = [
        {"role": "user", "content": "Line1\n"},
        {"role": "assistant", "content": "Line2\nLine3"},
    ]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 1.21μs -> 1.41μs (13.9% slower)

def test_content_with_long_string():
    # Message with a very long string
    long_str = "a" * 1000
    messages = [{"role": "user", "content": long_str}]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 1.00μs -> 1.13μs (11.0% slower)



def test_message_missing_role_key():
    # Role key is missing, should not affect concatenation
    messages = [
        {"content": "Hello"},
        {"content": "World"},
    ]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 1.30μs -> 1.53μs (14.8% slower)

def test_message_with_extra_keys():
    # Message dict contains extra keys
    messages = [
        {"role": "user", "content": "A", "timestamp": 12345, "id": 1},
        {"role": "assistant", "content": "B", "foo": "bar"},
    ]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 1.23μs -> 1.41μs (12.4% slower)

def test_content_is_none():
    # Message with content None should raise TypeError
    messages = [{"role": "user", "content": None}]
    with pytest.raises(TypeError):
        messages_to_prompt_string(messages) # 2.00μs -> 3.66μs (45.4% slower)

def test_content_is_integer():
    # Message with integer content should raise TypeError
    messages = [{"role": "user", "content": 123}]
    with pytest.raises(TypeError):
        messages_to_prompt_string(messages) # 2.20μs -> 3.60μs (39.0% slower)

def test_content_is_list():
    # Message with list as content should raise TypeError
    messages = [{"role": "user", "content": ["a", "b"]}]
    with pytest.raises(TypeError):
        messages_to_prompt_string(messages) # 1.94μs -> 3.27μs (40.9% slower)

def test_message_is_not_dict():
    # Message item is not a dict, should raise TypeError
    messages = ["not_a_dict"]
    with pytest.raises(TypeError):
        messages_to_prompt_string(messages) # 1.28μs -> 1.17μs (9.61% faster)

def test_message_missing_content_key():
    # Message dict missing 'content' key should raise KeyError
    messages = [{"role": "user"}]
    with pytest.raises(KeyError):
        messages_to_prompt_string(messages) # 1.15μs -> 1.09μs (5.23% faster)

# ---------------- Large Scale Test Cases ----------------

def test_large_number_of_messages():
    # Test with 1000 messages of single character each
    messages = [{"role": "user", "content": "a"} for _ in range(1000)]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 97.1μs -> 70.5μs (37.7% faster)


def test_large_message_with_long_content():
    # Test with 100 messages, each with 10-character string
    messages = [{"role": "user", "content": "abcdefghij"} for _ in range(100)]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 13.9μs -> 9.88μs (40.8% faster)



#------------------------------------------------
import typing as t

# imports
import pytest  # used for our unit tests
from guardrails.utils.docs_utils import messages_to_prompt_string


class Prompt:
    def __init__(self, source):
        self.source = source

class Instructions:
    def __init__(self, source):
        self.source = source

class MessageHistory(list):
    """Dummy MessageHistory for testing (inherits from list)."""
    pass
from guardrails.utils.docs_utils import messages_to_prompt_string

# unit tests

# --- Basic Test Cases ---

def test_single_message_with_str_content():
    # Test with a single message containing a string
    messages = [{"role": "user", "content": "Hello"}]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 1.07μs -> 1.24μs (13.3% slower)

def test_multiple_messages_with_str_content():
    # Test with multiple messages containing strings
    messages = [
        {"role": "system", "content": "System info."},
        {"role": "user", "content": "User question."},
        {"role": "assistant", "content": "Assistant reply."}
    ]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 1.50μs -> 1.66μs (9.24% slower)




def test_messagehistory_works_like_list():
    # Test with MessageHistory type (should behave like a list)
    messages = MessageHistory([
        {"role": "user", "content": "Hello"},
        {"role": "assistant", "content": "World"}
    ])
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 1.30μs -> 1.55μs (16.1% slower)

# --- Edge Test Cases ---

def test_empty_messages_list_returns_empty_string():
    # Test with an empty list
    messages = []
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 510ns -> 772ns (33.9% slower)

def test_message_with_empty_string_content():
    # Test with a message containing an empty string
    messages = [{"role": "user", "content": ""}]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 976ns -> 1.16μs (15.7% slower)




def test_message_with_whitespace_content():
    # Test with content that is only whitespace
    messages = [{"role": "user", "content": "   "}]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 971ns -> 1.23μs (21.1% slower)

def test_message_with_special_characters():
    # Test with content containing special characters
    messages = [{"role": "user", "content": "!@#$%^&*()_+-=[]{};':,.<>/?\n\t"}]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 970ns -> 1.21μs (19.8% slower)

def test_message_with_unicode_content():
    # Test with content containing unicode characters
    messages = [{"role": "user", "content": "你好,世界🌏"}]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 964ns -> 1.18μs (18.2% slower)

def test_message_with_duplicate_roles():
    # Test with messages having duplicate roles
    messages = [
        {"role": "user", "content": "A"},
        {"role": "user", "content": "B"}
    ]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 1.32μs -> 1.38μs (4.27% slower)

def test_message_with_missing_role_key():
    # Test with a message missing the 'role' key
    messages = [{"content": "No role here"}]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 1.01μs -> 1.12μs (10.5% slower)

def test_message_with_extra_keys():
    # Test with messages having extra keys
    messages = [{"role": "user", "content": "Hello", "extra": 42}]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 1.03μs -> 1.20μs (14.4% slower)

def test_message_with_content_key_missing():
    # Test with a message missing the 'content' key
    messages = [{"role": "user"}]
    with pytest.raises(KeyError):
        messages_to_prompt_string(messages) # 1.06μs -> 1.20μs (11.0% slower)

def test_message_is_not_dict():
    # Test with a message that is not a dict
    messages = ["just a string"]
    with pytest.raises(TypeError):
        messages_to_prompt_string(messages) # 1.17μs -> 1.18μs (1.27% slower)

def test_message_list_contains_non_dict():
    # Test with a list containing a dict and a non-dict
    messages = [{"role": "user", "content": "Hello"}, "not a dict"]
    with pytest.raises(TypeError):
        messages_to_prompt_string(messages) # 1.59μs -> 1.62μs (2.10% slower)

# --- Large Scale Test Cases ---

def test_large_number_of_messages_str_content():
    # Test with a large number of messages (all strings)
    messages = [{"role": "user", "content": "x"} for _ in range(1000)]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 96.5μs -> 72.1μs (33.9% faster)


def test_large_message_content():
    # Test with one message containing a very large string
    large_text = "a" * 10000
    messages = [{"role": "user", "content": large_text}]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 994ns -> 1.21μs (17.9% slower)

def test_large_messagehistory():
    # Test with MessageHistory containing many messages
    messages = MessageHistory([{"role": "user", "content": str(i)} for i in range(500)])
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 50.5μs -> 37.4μs (35.0% faster)
    expected = "".join(str(i) for i in range(500))

def test_large_scale_with_special_characters():
    # Test with many messages, each with special characters
    messages = [{"role": "user", "content": "!@#"} for _ in range(1000)]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 100μs -> 72.4μs (38.6% faster)

def test_large_scale_with_unicode():
    # Test with many messages, each with unicode characters
    messages = [{"role": "user", "content": "🌟"} for _ in range(1000)]
    codeflash_output = messages_to_prompt_string(messages); result = codeflash_output # 103μs -> 76.9μs (34.0% faster)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

To edit these changes git checkout codeflash/optimize-messages_to_prompt_string-mh1qj8pl and push.

Codeflash

The optimized code achieves a **29% speedup** through two key performance improvements:

**1. String concatenation optimization:** The original code uses `messages_copy += content` which creates a new string object on each iteration - an O(n²) operation for large inputs. The optimized version collects strings in a list and uses `"".join(content_list)` at the end, which is O(n) and much more efficient.

**2. Reduced isinstance() calls:** Instead of calling `isinstance()` twice per message (once for `Prompt`, once for `Instructions`), the optimized version uses a single call with a pre-defined tuple `_prompt_types = (Prompt, Instructions)`. This reduces function call overhead.

**3. Minimized attribute access:** The optimized code assigns `msg["content"]` to `content_obj` once per iteration, avoiding repeated dictionary lookups.

The performance gains are most pronounced with **large-scale inputs**: test cases with 500-1000 messages show 33-40% speedups, while small inputs (1-3 messages) are slightly slower due to the setup overhead of creating the list and tuple. The optimization particularly shines when processing many messages, making it ideal for batch processing scenarios or applications handling extensive message histories.

All behavior is preserved - same outputs, same exception handling, and same type support for `Prompt`, `Instructions`, and plain strings.
@codeflash-ai codeflash-ai bot requested a review from mashraf-222 October 22, 2025 08:32
@codeflash-ai codeflash-ai bot added the ⚡️ codeflash Optimization PR opened by Codeflash AI label Oct 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants